1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.systems.input;
12 import hip.util.data_structures;
13 import hip.error.handler;
14 
15 version(WebAssembly) version = QueuePopulatedExternally;
16 else version(UWP) version = QueuePopulatedExternally;
17 else version(PSVita) version = QueuePopulatedExternally;
18 else version(AppleOS) version = QueuePopulatedExternally;
19 
20 version(Android)
21 {
22     import hip.jni.helper.androidlog;
23     import hip.jni.jni;
24     import hip.jni.helper.jnicall;
25 
26     ///Setups an Android Package for HipremeEngine
27     alias HipAndroidInput = javaGetPackage!("com.hipremeengine.app.HipInput");
28     alias HipAndroidRenderer = javaGetPackage!("com.hipremeengine.app.Hip_GLES30_Renderer");
29 
30     @JavaFunc!(HipAndroidInput) void onMotionEventActionMove(int pointerId, float x, float y)
31 	{
32         HipEventQueue.post(0, HipEventQueue.EventType.touchMove, HipEventQueue.Touch(cast(ushort)pointerId, x,y));
33 	}
34 
35     @JavaFunc!(HipAndroidInput) void onMotionEventActionPointerDown(int pointerId, float x, float y)
36 	{
37         HipEventQueue.post(0, HipEventQueue.EventType.touchDown, HipEventQueue.Touch(cast(ushort)pointerId, x,y));
38 	}
39     @JavaFunc!(HipAndroidInput) void onMotionEventActionPointerUp(int pointerId, float x, float y)
40 	{
41         HipEventQueue.post(0, HipEventQueue.EventType.touchUp, HipEventQueue.Touch(cast(ushort)pointerId, x,y));
42 	}
43     @JavaFunc!(HipAndroidInput) void onMotionEventActionScroll(float x, float y)
44 	{
45         HipEventQueue.post(0, HipEventQueue.EventType.touchScroll, HipEventQueue.Scroll(x,x,0));
46 	}
47 
48     @JavaFunc!(HipAndroidRenderer) void onRendererResize(int x, int y)
49     {
50         ///Must be executed on the render thread :(
51         import hip.hiprenderer;
52         import hip.graphics.g2d.renderer2d;
53         HipRenderer.setWindowSize(x, y);
54         resizeRenderer2D(cast(uint)x, cast(uint)y);
55         // HipEventQueue.post(0, HipEventQueue.EventType.windowResize, HipEventQueue.Resize(cast(uint)x, cast(uint)y));
56     }
57 
58     mixin javaGenerateModuleMethodsForPackage!(HipAndroidInput, hip.systems.input, false);
59     mixin javaGenerateModuleMethodsForPackage!(HipAndroidRenderer, hip.systems.input, false);
60 }
61 else version(QueuePopulatedExternally)
62 {
63     export extern(System)
64     {
65         void HipInputOnTouchPressed(uint id, float x, float y)
66         {
67             HipEventQueue.post(0, HipEventQueue.EventType.touchDown, HipEventQueue.Touch(cast(ushort)id, x, y));
68         }
69         void HipInputOnTouchMoved(uint id, float x, float y)
70         {
71             HipEventQueue.post(0, HipEventQueue.EventType.touchMove, HipEventQueue.Touch(cast(ushort)id, x, y));
72         }
73         void HipInputOnTouchReleased(uint id, float x, float y)
74         {
75             HipEventQueue.post(0, HipEventQueue.EventType.touchUp, HipEventQueue.Touch(cast(ushort)id, x, y));
76         }
77         void HipInputOnTouchScroll(float x, float y, float z)
78         {
79             HipEventQueue.post(0, HipEventQueue.EventType.touchScroll, HipEventQueue.Scroll(x,y,z));
80         }
81         void HipInputOnKeyDown(uint virtualKey)
82         {
83             import hip.event.dispatcher;
84             HipEventQueue.post(0, HipEventQueue.EventType.keyDown, HipEventQueue.Key(cast(ushort)virtualKey.getHipKeyFromSystem));
85         }
86         void HipInputOnKeyUp(uint virtualKey)
87         {
88             import hip.event.dispatcher;
89             HipEventQueue.post(0, HipEventQueue.EventType.keyUp, HipEventQueue.Key(cast(ushort)virtualKey.getHipKeyFromSystem));
90         }
91         void HipInputOnGamepadConnected(ubyte id, ubyte type)
92         {
93             HipEventQueue.post(0, HipEventQueue.EventType.gamepadConnected, HipEventQueue.Gamepad(id, type));
94         }
95         void HipInputOnGamepadDisconnected(ubyte id, ubyte type)
96         {
97             HipEventQueue.post(0, HipEventQueue.EventType.gamepadDisconnected, HipEventQueue.Gamepad(id, type));
98         }
99         void HipOnRendererResize(int x, int y)
100         {
101             HipEventQueue.post(0, HipEventQueue.EventType.windowResize, HipEventQueue.Resize(cast(uint)x, cast(uint)y));
102         }
103     }    
104 } 
105 
106 /**
107 *   High efficient(at least memory-wise), tightly packed Input queue that supports any kind of data in
108 *   a single allocated memory pool(no fragmentation).
109 *
110 *   The input queue is populated by external APIs, like UWP's CoreWindow, Android's app and SDL2 Event. 
111 *   This way, it is possible to create a centralized input resource. This class does not creates the entire
112 *   input system. It creates its base for being handled.
113 */
114 class HipEventQueue : EventQueue
115 {
116     enum EventType : ubyte
117     {
118         ///Mouse was basically ignored for the sake of making it only touch ( it should be easier )
119         touchDown,
120         touchMove,
121         touchUp,
122         touchScroll,
123         keyDown,
124         keyUp,
125 
126         gamepadConnected,
127         gamepadDisconnected,
128 
129         ///When user returns to application
130         focusReceived,
131         ///When user exists the application
132         focusLost,
133         windowResize,
134         exit
135     }
136 
137     struct InputEvent
138     {
139         EventType type;
140         ubyte evSize;
141         void[0] evData;
142         pragma(inline, true)
143         T get(T)(){return *(cast(T*)evData);}
144     }
145 
146     struct Key
147     {
148         ushort id;
149     }
150 
151     struct Touch
152     {
153         ///Ubyte unnecessary, as I doubt any arch would receive a struct non divisible by 2
154         ushort id;
155         float  xPos;
156         float  yPos;
157     }
158     struct Resize
159     {
160         uint width;
161         uint height;
162     }
163 
164     struct Gamepad
165     {
166         ubyte id;
167         ///See hip.systems.gamepad.HipGamepadTypes
168         ubyte type;
169     }
170 
171     struct Scroll
172     {
173         float x, y,z;
174     }
175 
176     ///This class should probably not contain more than one instance(unless 2 people are playing)
177     protected __gshared HipEventQueue[] controllers;
178 
179     protected this(uint touchStructsCapacity = 126)
180     {
181         ///Uses capacity*greatest structure size
182         super(cast(uint)Touch.sizeof*touchStructsCapacity);
183     }
184     InputEvent* poll(){return cast(InputEvent*)super.poll();}
185 
186     static HipEventQueue newController(uint touchStructsCapacity = 126)
187     {
188         HipEventQueue ip = new HipEventQueue(touchStructsCapacity);
189         controllers~= ip;
190         return ip;
191     }
192 
193     /** External API used for getting the input events inside an internal queue. This way the API can remains the same*/
194     static void post(T)(uint id, EventType type, T ev)
195     {
196         import hip.util.format;
197         if(id >= controllers.length)
198         {
199             ErrorHandler.assertExit(false, format!("Input controller out of range!(ID: %s, Type: %s)")(id, type));
200         }
201         controllers[id].post(cast(ubyte)type, ev);
202     }
203 
204     /** External API used for getting the input events inside an internal queue. This way the API can remains the same*/
205     static void post(uint id, EventType type, Gamepad ev)
206     {
207         if(type == EventType.gamepadConnected)
208         {
209             while(controllers.length < id+1)
210                 newController();
211         }
212         import hip.util.format;
213         if(id >= controllers.length)
214         {
215             ErrorHandler.assertExit(false, format!("Input controller out of range!(ID: %s, Type: %s)")(id, type));
216         }
217         controllers[id].post(cast(ubyte)type, ev);
218     }
219 
220     /** Polls an input event for a specified controller */
221     static InputEvent* poll(uint id)
222     {
223         import hip.util.format;
224         if(id >= controllers.length)
225         {
226             ErrorHandler.assertExit(false, format!("Input controller out of range!(ID: %s)")(id));
227         }
228         return controllers[id].poll();
229     }
230     static void clear(uint id)
231     {
232         import hip.util.format;
233         if(id >= controllers.length)
234         {
235             ErrorHandler.assertExit(false, format!("Input controller out of range!(ID: %s)")(id));
236         }
237         controllers[id].clear();
238     }
239     alias poll = EventQueue.poll;
240     alias post = EventQueue.post;
241     alias clear = EventQueue.clear;
242 }